/**
 * Author(s): xuming.Wong <xuming.Wong@gmail.com>
 */
#include "M2MeshLoadImpl.h"

#include "OgreMath.h"
#include "OgreMesh.h"
#include "OgreSubMesh.h"
#include "OgreMaterial.h"
#include "OgreMaterialManager.h"
#include "OgreHardwareBufferManager.h"
#include "OgreSkeletonManager.h"
#include "OgreSkeleton.h"
#include "OgreBone.h"
#include "OgreStringConverter.h"

#include "GEngine.h"
#include "WOWArchive.h"
#include "M2File.h"
#include "M2SkinFile.h"
#include "M2MaterialLoader.h"
#include "M2ModelInfo.h"
#include "ChrHairGeoDbc.h"

namespace GNgine
{
	M2MeshLoadImpl::M2MeshLoadImpl(Ogre::Mesh *mesh) 
		: _m2File(0), _skinFile(0), _mesh(mesh),_m2Model(0)
	{

	}

	M2MeshLoadImpl::~M2MeshLoadImpl()
	{
		if(_m2Model){
			delete _m2Model;
		}
	}

	/*
	*/
	void M2MeshLoadImpl::load(std::string path)
	{
		_m2Model=M2Model::get(path);
		//initM2Model();
		//create mesh material for all submesh use;
		_m2File=_m2Model->getM2File();
		_skinFile=_m2Model->getSkinFile();
		

		_m2SubMeshCount=_skinFile->getSubMeshCount();
		_m2TexUnits=_skinFile->getTexUnitPtr();
		_m2SubMeshes=_skinFile->getSubMeshPtr();
		_m2Indices=_skinFile->getIndicesPtr();
		_m2Vertices=_m2File->getVerticesPtr();
		
		initM2Skeleton();
		initM2SubMeshes();
	}

	void M2MeshLoadImpl::initM2Skeleton()
	{
		Ogre::SkeletonPtr skel=Ogre::SkeletonManager::getSingleton().load(_m2Model->getSkeletonName(),_mesh->getGroup());
		if(!skel.isNull()){
			Ogre::String skeletonName=skel->getName();
			if(!skeletonName.empty()){
				_mesh->setSkeletonName(skeletonName);
			}
		}
	}

	void M2MeshLoadImpl::initM2SubMeshes()
	{
		for(int i=0;i<_m2SubMeshCount;i++){
			M2ModelSubMesh modelSub= _m2Model->getSubMesh(i);
			if(modelSub.show){
				Ogre::SubMesh* submesh=createOgreSubMesh(_m2SubMeshes[i]);
				submesh->setMaterialName(modelSub.materialName);
			}
		}

		_mesh->_setBounds(Ogre::AxisAlignedBox(_minAABB,_maxAABB),false);
		_mesh->_setBoundingSphereRadius(Ogre::Math::Sqrt(_radiusAABB));
	}

	
	Ogre::SubMesh* M2MeshLoadImpl::createOgreSubMesh(M2SubMesh m2SubMesh)
	{
		Ogre::SubMesh* submesh=_mesh->createSubMesh();
		
		//create material
		submesh->vertexData=new Ogre::VertexData();
		submesh->vertexData->vertexCount=m2SubMesh.vcount;
		submesh->useSharedVertices=false;
		submesh->operationType=Ogre::RenderOperation::OT_TRIANGLE_LIST;

		_vertexSize=0;
		/// fill in the declaration
		Ogre::VertexDeclaration* decl=submesh->vertexData->vertexDeclaration;
		///position
		decl->addElement(0,_vertexSize,Ogre::VET_FLOAT3,Ogre::VES_POSITION);
		_vertexSize+=Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3);

		///no blending wights

		//normal
		decl->addElement(0,_vertexSize,Ogre::VET_FLOAT3,Ogre::VES_NORMAL);
		_vertexSize+=Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3);

		//texture coord,can be many texture layers?
		decl->addElement(0, _vertexSize, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES);
		_vertexSize += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT2);

		/// the initial vertex buffer
		Ogre::HardwareVertexBufferSharedPtr vertexBuf= 
			Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(
			_vertexSize,submesh->vertexData->vertexCount,Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY);

		///bind the source
		Ogre::VertexBufferBinding* bind=submesh->vertexData->vertexBufferBinding;
		bind->setBinding(0,vertexBuf);

		///index  buffer
		int numIndices=m2SubMesh.icount;//for icount is unsigned short so 16bit used

		Ogre::HardwareIndexBufferSharedPtr indexBuf=
			Ogre::HardwareBufferManager::getSingleton().createIndexBuffer(
			Ogre::HardwareIndexBuffer::IT_16BIT,numIndices,Ogre::HardwareBuffer::HBU_WRITE_ONLY);

		submesh->indexData->indexCount=numIndices;
		submesh->indexData->indexBuffer=indexBuf;

		///build index items(16bit)
		unsigned short* indices16=static_cast<unsigned short*>(indexBuf->lock(Ogre::HardwareBuffer::HBL_NORMAL));

		for(int i=0;i<numIndices;i++){
			int index=_m2Indices[m2SubMesh.istart+ i]-m2SubMesh.vstart;//global to local
			if(index<0){
				assert(true);
			}
			*indices16++=(unsigned short)index;
			initVertexBoneAssignment(submesh,index);
		}

		indexBuf->unlock();

		/// build vertex items
		unsigned char* vertexdata=static_cast<unsigned char*>(vertexBuf->lock(Ogre::HardwareBuffer::HBL_NORMAL));
		Ogre::VertexData* vdata=submesh->vertexData;
		Ogre::VertexDeclaration::VertexElementList elements=decl->findElementsBySource(0);
		float* floatdata=0;
		
		int numVertices=m2SubMesh.vcount;
		for(int i=0;i<numVertices;i++){
			M2Vertex m2Vertex=_m2Vertices[m2SubMesh.vstart+i];

			Ogre::VertexDeclaration::VertexElementList::const_iterator it=elements.begin(),itEnd=elements.end();
			for(;it!=itEnd;++it){//this tranverse vertext dec types
				Ogre::VertexElement vertexElement=(*it);
				int sematic=vertexElement.getSemantic();
				if(sematic==Ogre::VES_POSITION){//load postion info
					vertexElement.baseVertexPointerToElement(vertexdata,&floatdata);

					Ogre::Vector3 pos=fixCoord(Ogre::Vector3(m2Vertex.pos));
					
					*floatdata++= pos.x;
					*floatdata++= pos.y;
					*floatdata++= pos.z;
					
					if(i==0){
						_minAABB=_maxAABB=pos;
						_radiusAABB=_minAABB.squaredLength();
					}else{
						_minAABB.makeFloor(pos);
						_maxAABB.makeCeil(pos);
						_radiusAABB= std::max(pos.squaredLength(), _radiusAABB);
					}

				}else if(sematic==Ogre::VES_NORMAL){
					vertexElement.baseVertexPointerToElement(vertexdata,&floatdata);
					
					Ogre::Vector3 normal=fixCoord(Ogre::Vector3(m2Vertex.normal));
					normal.normalise();
					*floatdata++= normal.x;
					*floatdata++= normal.y;
					*floatdata++= normal.z;

				}else if(sematic== Ogre::VES_TEXTURE_COORDINATES){
					vertexElement.baseVertexPointerToElement(vertexdata, &floatdata);

					Ogre::Vector2 texcoord=Ogre::Vector2(m2Vertex.texcoords);
					*floatdata++= texcoord.x;
					*floatdata++= texcoord.y;
					
				}else if(sematic==Ogre::VES_DIFFUSE){
					
				}
			}

			vertexdata+=vertexBuf->getVertexSize();

		}

		vertexBuf->unlock();

		//submes
		return submesh;
	}

	void M2MeshLoadImpl::initVertexBoneAssignment(Ogre::SubMesh* submesh, int vertexIndex)
	{
		M2Vertex vertex=_m2Vertices[vertexIndex];
		for (int b=0; b<4; b++) {//b bone index		
			if (vertex.weights[b]>0) {
				float weight=((float)vertex.weights[b])/255.0f;
				
				int boneIndex=vertex.boneIndices[b];
				unsigned short boneHandle=_m2Model->getBoneHandle(boneIndex);

				Ogre::VertexBoneAssignment vba;
				vba.vertexIndex=vertexIndex;
				vba.boneIndex=boneHandle;
				vba.weight=weight;

				submesh->addBoneAssignment(vba);
				
			}
		}
	}

}
